package aceim.protocol.snuk182.xmpp.common; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Random; import java.util.concurrent.Executors; import org.jivesoftware.smack.Chat; import org.jivesoftware.smack.ChatManagerListener; import org.jivesoftware.smack.PacketListener; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smackx.ChatState; import org.jivesoftware.smackx.ChatStateListener; import org.jivesoftware.smackx.DefaultMessageEventRequestListener; import org.jivesoftware.smackx.Form; import org.jivesoftware.smackx.FormField; import org.jivesoftware.smackx.MessageEventManager; import org.jivesoftware.smackx.MessageEventNotificationListener; import org.jivesoftware.smackx.muc.Affiliate; import org.jivesoftware.smackx.muc.HostedRoom; import org.jivesoftware.smackx.muc.InvitationRejectionListener; import org.jivesoftware.smackx.muc.MultiUserChat; import org.jivesoftware.smackx.muc.ParticipantStatusListener; import org.jivesoftware.smackx.muc.RoomInfo; import org.jivesoftware.smackx.muc.SubjectUpdatedListener; import aceim.api.dataentity.Buddy; import aceim.api.dataentity.BuddyGroup; import aceim.api.dataentity.InputFormFeature; import aceim.api.dataentity.ItemAction; import aceim.api.dataentity.MessageAckState; import aceim.api.dataentity.MultiChatRoom; import aceim.api.dataentity.OnlineInfo; import aceim.api.dataentity.PersonalInfo; import aceim.api.dataentity.ServiceMessage; import aceim.api.dataentity.TextMessage; import aceim.api.service.ApiConstants; import aceim.api.service.ProtocolException; import aceim.api.utils.Logger; import aceim.api.utils.Logger.LoggerLevel; import android.text.TextUtils; public class XMPPChatListener extends XMPPListener implements ChatManagerListener, ChatStateListener, MessageEventNotificationListener { private static final Random RANDOMIZER = new Random(); private final Map<String, Chat> chats = new HashMap<String, Chat>(); private final Map<String, MultiUserChat> multichats = new HashMap<String, MultiUserChat>(); private final DefaultMessageEventRequestListener messageEventListener = new DefaultMessageEventRequestListener(); private MessageEventManager messageEventManager; private final Runnable getGroupchatsRunnable = new Runnable() { @Override public void run() { try { getAvailableChatRooms(); } catch (ProtocolException e) { Logger.log(e); } } }; public XMPPChatListener(XMPPServiceInternal service) { super(service); } @Override public void chatCreated(Chat chat, boolean isLocal) { Logger.log("chat " + chat.getParticipant(), LoggerLevel.VERBOSE); if (chats.get(getInternalService().getService().getEntityAdapter().normalizeJID(chat.getParticipant())) == null) { chat.addMessageListener(this); chats.put(getInternalService().getService().getEntityAdapter().normalizeJID(chat.getParticipant()), chat); } } @Override public void processMessage(Chat chat, Message message) { if (message.getBody() == null) { Logger.log("Empty message from " + chat.getParticipant(), LoggerLevel.WARNING); } else { Logger.log("Message from " + chat.getParticipant(), LoggerLevel.VERBOSE); processMessageInternal(message, false); } } @Override public void stateChanged(Chat chat, ChatState state) { if (state == ChatState.composing) { getInternalService().getService().getCoreService().typingNotification(getInternalService().getService().getEntityAdapter().normalizeJID(chat.getParticipant())); } } @Override public void cancelledNotification(String from, String packetID) { // TODO Auto-generated method stub } @Override public void composingNotification(String from, String packetID) { getInternalService().getService().getCoreService().typingNotification(getInternalService().getService().getEntityAdapter().normalizeJID(from)); } @Override public void deliveredNotification(String from, String packetID) { long messageId = Long.parseLong(packetID); Logger.log(getInternalService().getService().getProtocolUid() + " - " + from + " delivered " + messageId, LoggerLevel.VERBOSE); getInternalService().getService().getCoreService().messageAck(getInternalService().getService().getEntityAdapter().normalizeJID(from), messageId, MessageAckState.RECIPIENT_ACK); } @Override public void displayedNotification(String from, String packetID) { long messageId = Long.parseLong(packetID); Logger.log(getInternalService().getService().getProtocolUid() + " - " + from + " displayed " + messageId, LoggerLevel.VERBOSE); getInternalService().getService().getCoreService().messageAck(getInternalService().getService().getEntityAdapter().normalizeJID(from), messageId, MessageAckState.READ_ACK); } @Override public void offlineNotification(String from, String packetID) { long messageId = Long.parseLong(packetID); Logger.log(getInternalService().getService().getProtocolUid() + " - " + from + " offline " + messageId, LoggerLevel.VERBOSE); getInternalService().getService().getCoreService().messageAck(getInternalService().getService().getEntityAdapter().normalizeJID(from), messageId, MessageAckState.SERVER_ACK); } void processMessageInternal(final Message message, boolean resourceAsWriterId) { TextMessage txtmessage = getInternalService().getService().getEntityAdapter().xmppMessage2TextMessage(message, getInternalService(), resourceAsWriterId); getInternalService().getService().getCoreService().message(txtmessage); } void sendTyping(String jid) { messageEventManager.sendComposingNotification(jid, Integer.toHexString(RANDOMIZER.nextInt())); } long sendMessage(TextMessage textMessage) throws Exception { MultiUserChat muc = multichats.get(textMessage.getContactUid()); if (muc != null) { Message m = muc.createMessage(); m.setBody(textMessage.getText()); muc.sendMessage(m); return m.getPacketID().hashCode(); } Chat chat = chats.get(textMessage.getContactUid()); if (chat == null) { chat = getInternalService().getConnection().getChatManager().createChat(textMessage.getContactUid(), this); chats.put(textMessage.getContactUid(), chat); } Message packet = getInternalService().getService().getEntityAdapter().textMessage2XMPPMessage(textMessage, chat.getThreadID(), chat.getParticipant(), Message.Type.chat); chat.sendMessage(packet); return packet.getPacketID().hashCode(); } void chatDefaultAction(String chatId, String serviceMessage) { try { ServiceMessage message = new ServiceMessage(getInternalService().getService().getServiceId(), chatId, false); message.setText(serviceMessage); getInternalService().getService().getCoreService().message(message); getInternalService().getService().getCoreService().multiChatParticipants(chatId, getChatRoomOccupants(chatId, false)); } catch (ProtocolException e) { // TODO Auto-generated catch block e.printStackTrace(); } } List<BuddyGroup> getChatRoomOccupants(String chatId, boolean loadOccupantIcons) throws ProtocolException { MultiUserChat muc = multichats.get(chatId); if (muc == null) { throw new ProtocolException("No joined chat found"); } return getInternalService().getService().getEntityAdapter().xmppMUCOccupants2mcrOccupants(getInternalService(), muc, loadOccupantIcons); } /** * @return the messageEventListener */ public DefaultMessageEventRequestListener getMessageEventListener() { return messageEventListener; } /** * @param messageEventManager the messageEventManager to set */ public void setMessageEventManager(MessageEventManager messageEventManager) { this.messageEventManager = messageEventManager; if (messageEventManager == null){ return; } if (getInternalService().getOnlineInfo().getFeatures().getByte(ApiConstants.FEATURE_STATUS, (byte) 0) != XMPPEntityAdapter.INVISIBLE_STATUS_ID) { messageEventManager.addMessageEventRequestListener(messageEventListener); } messageEventManager.addMessageEventNotificationListener(this); } boolean amIOwner(MultiUserChat chat) throws XMPPException { for (Affiliate aff : chat.getOwners()) { if (getInternalService().getService().getEntityAdapter().normalizeJID(aff.getJid()).equals(getInternalService().getService().getProtocolUid())) { return true; } } return false; } void fillWithListeners(final MultiUserChat chat) { chat.addMessageListener(new PacketListener() { @Override public void processPacket(Packet packet) { Message message = (Message) packet; if (message.getFrom().split("/")[1].equals(chat.getNickname())) { Logger.log("Message from myself!"); return; } processMessageInternal(message, true); } }); chat.addParticipantListener(new PacketListener() { @Override public void processPacket(Packet packet) { try { getInternalService().getService().getCoreService().multiChatParticipants(chat.getRoom(), getChatRoomOccupants(chat.getRoom(), false)); } catch (ProtocolException e) { Logger.log(e); } } }); chat.addInvitationRejectionListener(new InvitationRejectionListener() { @Override public void invitationDeclined(String invitee, String reason) { chatDefaultAction(chat.getRoom(), StringUtils.parseResource(invitee) + " declined invitation: " + reason); } }); chat.addParticipantStatusListener(new ParticipantStatusListener() { @Override public void voiceRevoked(String participant) { chatDefaultAction(chat.getRoom(), StringUtils.parseResource(participant) + " voice revoked"); } @Override public void voiceGranted(String participant) { chatDefaultAction(chat.getRoom(), StringUtils.parseResource(participant) + " voice granted"); } @Override public void ownershipRevoked(String participant) { chatDefaultAction(chat.getRoom(), StringUtils.parseResource(participant) + " ownership revoked"); } @Override public void ownershipGranted(String participant) { chatDefaultAction(chat.getRoom(), StringUtils.parseResource(participant) + " ownership granted"); } @Override public void nicknameChanged(String participant, String newNickname) { chatDefaultAction(chat.getRoom(), StringUtils.parseResource(participant) + " changed nick to " + newNickname); } @Override public void moderatorRevoked(String participant) { chatDefaultAction(chat.getRoom(), StringUtils.parseResource(participant) + " moderator revoked"); } @Override public void moderatorGranted(String participant) { chatDefaultAction(chat.getRoom(), StringUtils.parseResource(participant) + " moderator granted"); } @Override public void membershipRevoked(String participant) { chatDefaultAction(chat.getRoom(), StringUtils.parseResource(participant) + " membership revoked"); } @Override public void membershipGranted(String participant) { chatDefaultAction(chat.getRoom(), StringUtils.parseResource(participant) + " membership granted"); } @Override public void left(String participant) { chatDefaultAction(chat.getRoom(), StringUtils.parseResource(participant) + " left the chat"); } @Override public void kicked(String participant, String actor, String reason) { chatDefaultAction(chat.getRoom(), StringUtils.parseResource(participant) + " was kicked by " + actor + ": " + reason); } @Override public void joined(String participant) { chatDefaultAction(chat.getRoom(), StringUtils.parseResource(participant) + " joined the chat"); } @Override public void banned(String participant, String actor, String reason) { chatDefaultAction(chat.getRoom(), StringUtils.parseResource(participant) + " was banned by " + actor + ": " + reason); } @Override public void adminRevoked(String participant) { chatDefaultAction(chat.getRoom(), StringUtils.parseResource(participant) + " admin revoked"); } @Override public void adminGranted(String participant) { chatDefaultAction(chat.getRoom(), StringUtils.parseResource(participant) + " admin granted"); } }); chat.addSubjectUpdatedListener(new SubjectUpdatedListener() { @Override public void subjectUpdated(String subject, String from) { ServiceMessage sm = new ServiceMessage(getInternalService().getService().getServiceId(), chat.getRoom(), false); sm.setText(StringUtils.parseResource(from) + " set chat topic to \"" + subject + "\""); getInternalService().getService().getCoreService().message(sm); Buddy buddy = getInternalService().getService().getEntityAdapter().chatInfo2Buddy(chat.getRoom(), subject, getInternalService().getService().getProtocolUid(), getInternalService().getService().getServiceId(), true); getInternalService().getService().getCoreService().buddyAction(ItemAction.MODIFIED, buddy); } }); } List<MultiChatRoom> getJoinedChatRooms() { Iterator<String> joinedRooms = MultiUserChat.getJoinedRooms(getInternalService().getConnection(), getInternalService().getService().getProtocolUid()); List<MultiChatRoom> multiChatBuddies = new ArrayList<MultiChatRoom>(); for (; joinedRooms.hasNext();) { String roomJid = joinedRooms.next(); try { RoomInfo info = MultiUserChat.getRoomInfo(getInternalService().getConnection(), roomJid); multiChatBuddies.add(getInternalService().getService().getEntityAdapter().chatRoomInfo2Buddy(info, getInternalService().getService().getProtocolUid(), getInternalService().getService().getServiceId(), true)); } catch (XMPPException e) { Logger.log(e); } } for (final Buddy room : multiChatBuddies) { MultiUserChat chat = new MultiUserChat(getInternalService().getConnection(), room.getProtocolUid()); fillWithListeners(chat); multichats.put(room.getProtocolUid(), chat); } return multiChatBuddies; } @Override void onDisconnect() { chats.clear(); multichats.clear(); } public void requestAvailableGroupchats() { Executors.defaultThreadFactory().newThread(getGroupchatsRunnable).start(); } private void getAvailableChatRooms() throws ProtocolException { try { Collection<String> mucServices = MultiUserChat.getServiceNames(getInternalService().getConnection()); if (mucServices.isEmpty()) { throw new ProtocolException(ProtocolException.Cause.NO_GROUPCHAT_AVAILABLE); } List<PersonalInfo> chats = new ArrayList<PersonalInfo>(); for (String service : mucServices) { try { chats.addAll(getInternalService().getService().getEntityAdapter().xmppHostedRooms2MultiChatRooms(MultiUserChat.getHostedRooms(getInternalService().getConnection(), service), getInternalService().getService().getProtocolUid(), getInternalService().getService().getServiceId())); } catch (XMPPException e) { Logger.log(e); } } getInternalService().getService().getCoreService().searchResult(chats); } catch (XMPPException e) { Logger.log(e); } } public void leaveChat(final String chatId) { Runnable r = new Runnable() { @Override public void run() { MultiUserChat muc = multichats.remove(chatId); if (muc != null) { muc.leave(); OnlineInfo info = getInternalService().getRosterListener().getPresenceCache().get(chatId); info.getFeatures().putByte(ApiConstants.FEATURE_STATUS, (byte) -1); info.getFeatures().remove(XMPPApiConstants.FEATURE_CONFIGURE_CHAT_ROOM_ACTION); info.getFeatures().remove(XMPPApiConstants.FEATURE_CONFIGURE_CHAT_ROOM_RESULT); //getInternalService().getService().getCoreService().buddyAction(ItemAction.LEFT, buddy); getInternalService().getService().getCoreService().buddyStateChanged(Arrays.asList(info)); } } }; Executors.defaultThreadFactory().newThread(r).start(); } public void joinChat(final String host, final String chat, final String nickname, final String password, final boolean createChat) { Runnable r = new Runnable() { @Override public void run() { try { Collection<String> mucServices = MultiUserChat.getServiceNames(getInternalService().getConnection()); MultiUserChat muc = null; String chatJid = chat + "@" + host; String myName = TextUtils.isEmpty(nickname) ? StringUtils.parseName(getInternalService().getService().getProtocolUid()) : nickname; label: for (String service : mucServices) { for (HostedRoom room : MultiUserChat.getHostedRooms(getInternalService().getConnection(), service)) { if (room.getJid().equals(chatJid)) { muc = new MultiUserChat(getInternalService().getConnection(), chatJid); continue label; } } } if (muc == null) { muc = new MultiUserChat(getInternalService().getConnection(), chatJid); } else { if (createChat) { getInternalService().getService().getCoreService().notification(getInternalService().getService().getContext().getString(R.string.chat_exists, chatJid)); return; } } if (createChat) { newMultiUserChat(muc, nickname, password); } else { muc.join(myName, password); } multichats.put(chatJid, muc); RoomInfo info = MultiUserChat.getRoomInfo(getInternalService().getConnection(), chatJid); MultiChatRoom room = getInternalService().getService().getEntityAdapter().chatRoomInfo2Buddy(info, getInternalService().getService().getProtocolUid(), getInternalService().getService().getServiceId(), true); try { room.getOccupants().addAll(getInternalService().getService().getEntityAdapter().xmppMUCOccupants2mcrOccupants(getInternalService(), muc, true)); String myChatId = chatJid + "/" + myName; for (Affiliate aff: muc.getOwners()) { Logger.log("Owner: "+aff.getJid(), LoggerLevel.VERBOSE); if (aff.getJid().equals(myChatId) || aff.getJid().equals(getInternalService().getService().getProtocolUid())) { room.getOnlineInfo().getFeatures().putBoolean(XMPPApiConstants.FEATURE_CONFIGURE_CHAT_ROOM_ACTION, true); room.getOnlineInfo().getFeatures().putBoolean(XMPPApiConstants.FEATURE_DESTROY_CHAT_ROOM, true); } } for (Affiliate aff: muc.getAdmins()) { Logger.log("Admin: "+aff.getJid(), LoggerLevel.VERBOSE); if (aff.getJid().equals(myChatId) || aff.getJid().equals(getInternalService().getService().getProtocolUid())) { room.getOnlineInfo().getFeatures().putBoolean(XMPPApiConstants.FEATURE_CONFIGURE_CHAT_ROOM_ACTION, true); room.getOnlineInfo().getFeatures().putBoolean(XMPPApiConstants.FEATURE_DESTROY_CHAT_ROOM, true); } } } catch (Exception e) { Logger.log(e.getLocalizedMessage(), LoggerLevel.DEBUG); } room.setName(TextUtils.isEmpty(info.getDescription()) ? chat : info.getDescription()); room.getOnlineInfo().setXstatusName(muc.getSubject()); getInternalService().getRosterListener().getPresenceCache().put(chatJid, room.getOnlineInfo()); getInternalService().getService().getCoreService().buddyAction(ItemAction.JOINED, room); getInternalService().getService().getCoreService().buddyStateChanged(Arrays.asList(room.getOnlineInfo())); fillWithListeners(muc); } catch (XMPPException e) { getInternalService().onXmppException(e); } } }; Executors.defaultThreadFactory().newThread(r).start(); } private void newMultiUserChat(MultiUserChat muc, String nickname, String password) throws XMPPException { muc.create(nickname != null ? nickname : getInternalService().getOnlineInfo().getProtocolUid()); Form form = muc.getConfigurationForm(); Form submitForm = form.createAnswerForm(); for (Iterator<FormField> fields = form.getFields(); fields.hasNext();) { FormField field = fields.next(); Logger.log("Chat "+ muc.getRoom() + " form field "+field.getVariable()); if (!FormField.TYPE_HIDDEN.equals(field.getType()) && field.getVariable() != null) { submitForm.setDefaultAnswer(field.getVariable()); } } try { List<String> owners = new ArrayList<String>(); owners.add(getInternalService().getService().getProtocolUid()); submitForm.setAnswer("muc#roomconfig_roomowners", owners); } catch (Exception e) { Logger.log("Could not set XMPP muc room owners for " + muc.getRoom()); } if (password != null) { submitForm.setAnswer("muc#roomconfig_roomsecret", password); submitForm.setAnswer("muc#roomconfig_publicroom", false); submitForm.setAnswer("muc#roomconfig_passwordprotectedroom", true); } muc.sendConfigurationForm(submitForm); } public boolean hasGroupchatSupport() throws XMPPException { return !MultiUserChat.getServiceNames(getInternalService().getConnection()).isEmpty(); } public void getChatConfigurationForm(final String chatJid) { Runnable r = new Runnable() { @Override public void run() { MultiUserChat muc = multichats.get(chatJid); if (muc != null) { try { Form form = muc.getConfigurationForm(); InputFormFeature feature = getInternalService().getService().getEntityAdapter().chatRoomConfigurationForm2InputFormFeature(form, getInternalService().getService().getContext()); getInternalService().getService().getCoreService().showFeatureInputForm(chatJid, feature); } catch (XMPPException e) { getInternalService().onXmppException(e); } } else { Logger.log("Could not find a room with jid #" + chatJid, LoggerLevel.INFO); } } }; Executors.defaultThreadFactory().newThread(r).start(); } public void chatRoomConfiguration(final String chatJid, final Map<String, String> values) { Runnable r = new Runnable() { @Override public void run() { MultiUserChat muc = multichats.get(chatJid); if (muc != null) { try { Form form = muc.getConfigurationForm(); Form submitForm = fillMucForm(form, values); muc.sendConfigurationForm(submitForm); RoomInfo info = MultiUserChat.getRoomInfo(getInternalService().getConnection(), chatJid); MultiChatRoom room = getInternalService().getService().getEntityAdapter().chatRoomInfo2Buddy(info, getInternalService().getService().getProtocolUid(), getInternalService().getService().getServiceId(), true); try { room.getOccupants().addAll(getInternalService().getService().getEntityAdapter().xmppMUCOccupants2mcrOccupants(getInternalService(), muc, true)); } catch (Exception e) { Logger.log(e.getLocalizedMessage(), LoggerLevel.DEBUG); } room.setName(TextUtils.isEmpty(info.getDescription()) ? StringUtils.parseName(chatJid) : info.getDescription()); room.getOnlineInfo().setXstatusName(info.getSubject()); room.getOnlineInfo().getFeatures().putBoolean(XMPPApiConstants.FEATURE_CONFIGURE_CHAT_ROOM_ACTION, true); getInternalService().getService().getCoreService().buddyAction(ItemAction.JOINED, room); getInternalService().getService().getCoreService().buddyStateChanged(Arrays.asList(room.getOnlineInfo())); } catch (XMPPException e) { getInternalService().onXmppException(e); } } else { Logger.log("Could not find a room with jid #" + chatJid, LoggerLevel.INFO); } } }; Executors.defaultThreadFactory().newThread(r).start(); } private Form fillMucForm(Form form, Map<String, String> values) { Form submitForm = form.createAnswerForm(); for (Iterator<FormField> i = submitForm.getFields(); i.hasNext();) { FormField f = i.next(); String value = values.get(f.getLabel()); if (!TextUtils.isEmpty(value)) { String type = f.getType(); if (type.equalsIgnoreCase(FormField.TYPE_TEXT_SINGLE) || type.equalsIgnoreCase(FormField.TYPE_JID_SINGLE) || type.equalsIgnoreCase(FormField.TYPE_TEXT_PRIVATE) || type.equalsIgnoreCase(FormField.TYPE_TEXT_MULTI)) { submitForm.setAnswer(f.getVariable(), value); } else if (type.equalsIgnoreCase(FormField.TYPE_JID_MULTI) || type.equalsIgnoreCase(FormField.TYPE_LIST_MULTI) || type.equalsIgnoreCase(FormField.TYPE_LIST_SINGLE)) { submitForm.setAnswer(f.getVariable(), Arrays.asList(value)); } else if (type.equalsIgnoreCase(FormField.TYPE_BOOLEAN)) { submitForm.setAnswer(f.getVariable(), Boolean.parseBoolean(value)); } else { submitForm.setAnswer(f.getVariable(), value); } } else { submitForm.setDefaultAnswer(f.getVariable()); } } return submitForm; } public void destroyChatRoom(final String chatJid) { Runnable r = new Runnable() { @Override public void run() { MultiUserChat muc = multichats.get(chatJid); if (muc != null) { try { Buddy b = getInternalService().getService().getEntityAdapter().chatInfo2Buddy(chatJid, chatJid, getInternalService().getService().getProtocolUid(), getInternalService().getService().getServiceId(), false); muc.destroy(null, null); getInternalService().getService().getCoreService().buddyAction(ItemAction.DELETED, b); } catch (XMPPException e) { getInternalService().onXmppException(e); } } else { Logger.log("Could not find a room with jid #" + chatJid, LoggerLevel.INFO); } } }; Executors.defaultThreadFactory().newThread(r).start(); } }